﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using GlobalObjects;
//mongo
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.GridFS;
using MongoDB.Driver.Linq;
using Newtonsoft.Json;

namespace DataAccessLayer
{
    public class Mongo
    {
        public static string  defaultConnectionString = "mongodb://uclmongovm.cloudapp.net:27017";
        public static string defaultDatabase = "test";
        MongoDatabase database;
        public Mongo(string connectionString, string db) {
            database = getDatabase(connectionString,db);
        }
        /// <summary>
        /// returns a list of QueryEntity objects based on the given sensor id and time range. Each object represents the start time and end time based on the given
        /// page size. 
        /// </summary>
        /// <param name="sensorId">sensor ID</param>
        /// <param name="startTime">starting time for the query (must be UTC)</param>
        /// <param name="endTime">ending time for the query (must be UTC)</param>
        /// <param name="pageSize">page size</param>
        /// <returns>A list of QueryEntity objects that represent each page.</returns>
        public List<QueryEntity> getDataPages_DAL(int sensorId, DateTime startTime, DateTime endTime, int pageSize, string sensorType)
        {
            List<QueryEntity> list = new List<QueryEntity>();
            var collection = database.GetCollection(sensorType);
            //
            string startTimeString = startTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
            string endTimeString = endTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
            //
            var query = Query.And(Query.GTE("time", startTimeString), Query.LTE("time", endTimeString), Query.EQ("sensorId", sensorId));
            //
            MongoCursor countCursor = collection.Find(query).SetSortOrder(SortBy.Descending("time")).SetFields(Fields.Exclude("_id").Include("time"));
            long cursorCounter = countCursor.Count();
            int iteration = (int)cursorCounter/(int)pageSize;
            int remainder = (int)cursorCounter % (int)pageSize;
            int skip = 0;
            while (iteration>0)
            {
                iteration--;
                MongoCursor timeCursor = collection.Find(query).SetSortOrder(SortBy.Descending("time")).SetFields(Fields.Exclude("_id").Include("time"));
                timeCursor.Skip = skip;
                timeCursor.Limit = pageSize;
                int counter = 0;
                QueryEntity qEntity = new QueryEntity();
                foreach (var x in timeCursor)
                {
                    counter+=1;
                    qEntity.SensorId = sensorId;
                    //
                    JavaScriptSerializer js = new JavaScriptSerializer();
                    string json = x.ToJson();
                    dynamic item = js.Deserialize<object>(json);
                    object obj = item["time"];
                    string time = obj.ToString();
                    DateTime time2 = DateTime.ParseExact(time, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", null);             
                 //
                    if(counter.Equals(1)){
                        qEntity.EndTime = time2;
                    }
                    else if(counter.Equals(pageSize)){
                        qEntity.StartTime = time2;
                        qEntity.PageSize = pageSize;
                        list.Add(qEntity);
                    }
                }
                skip += pageSize;
            }
            int counter2 = 0;
            if(remainder!=0){
                MongoCursor timeCursor = collection.Find(query).SetSortOrder(SortBy.Descending("time")).SetFields(Fields.Exclude("_id").Include("time"));
                timeCursor.Skip = skip;
                QueryEntity qEntity = new QueryEntity();
                foreach (var x in timeCursor)
                {
                    counter2 += 1;
                    qEntity.SensorId = sensorId;
                    //
                    JavaScriptSerializer js = new JavaScriptSerializer();
                    string json = x.ToJson();
                    dynamic item = js.Deserialize<object>(json);
                    object obj = item["time"];
                    string time = obj.ToString();
                    DateTime time2 = DateTime.ParseExact(time, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", null);
             
                    if (counter2.Equals(1))
                    {
                        qEntity.EndTime = time2;
                    }
                    if (counter2.Equals(remainder))
                    {
                        qEntity.StartTime = time2;
                        qEntity.PageSize = remainder;
                        list.Add(qEntity);
                    }
                }
            }
            return list;
        }

        /// <summary>
        /// returns a json string of the retrieved data based on the given criteria. 
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <param name="startTime">start time (must be UTC)</param>
        /// <param name="endTime">end time(must be UTC)</param>
        /// <param name="pageSize">page size</param>
        /// <returns></returns>
        public string getTempAndHumadityByIdandTime_DAL(int sensorId, DateTime startTime, DateTime endTime, int pageSize, string sensorType)
        {
            //get Collection
            var collection = database.GetCollection(sensorType);
            string startTimeString = startTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
            string endTimeString = endTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
            //
            var query = Query.And(Query.GTE("time", startTimeString), Query.LTE("time", endTimeString), Query.EQ("sensorId", sensorId));
            //
            MongoCursor cursor = collection.Find(query).SetSortOrder(SortBy.Ascending("time")).SetFields(Fields.Exclude("_id").Include("time", "temperature", "humidity"));
            cursor.Limit = pageSize;
            string json = cursor.ToJson();
            return json;
        }
   
        /// <summary>
        /// Inserts new documents to the given collection based on the json that it receives as a string.
        /// </summary>
        /// <param name="jsonString">json object in string format</param>
        /// <param name="sensorId">sensor id</param>
        /// <param name="sensorType">sensor type (collection in the database)</param>
        public void deserialiseAndInsertDocument_DAL(string jsonString, int sensorId, string sensorType)
        {
            UserEntity userEntity = new UserEntity();
            SensorEntity sensorEntity = new SensorEntity();
            int userId = userEntity.getUserIdGivenSensorId_DAL(sensorId);
            string column = null;
            Nullable<Double> upperLimit = null;
            Nullable<Double> lowerLimit = null;
            if (userId != 0)
            {
                //UserDetails userDetails = userEntity.getUserById_DAL(userId);
                try
                {
                    Limits limit = sensorEntity.getLimits_DAL(sensorId);
                    if (limit.trigger_name != null)
                    {
                        column = limit.trigger_name;
                        if (limit.upper_limit != null)
                        {
                            upperLimit = limit.upper_limit.Value;
                        }
                        else
                        {
                            upperLimit = null;
                        }
                        if (limit.lower_limit != null)
                        {
                            lowerLimit = limit.lower_limit.Value;
                        }
                        else
                        {
                            lowerLimit = null;
                        }
                    }
                    else
                    {
                        column = null;
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
            try
            {
                JavaScriptSerializer js = new JavaScriptSerializer();
                dynamic item = js.Deserialize<object>(jsonString);
                // int id = item["sensorId"];
                object obj = item["data"];
                List<object> list = ((IEnumerable)obj).Cast<object>().ToList();
                string key;
                object value;
                foreach (Dictionary<string, object> dic in list)
                {
                    string document = "{ \"sensorId\": " + sensorId;
                    foreach (KeyValuePair<string, object> kv in dic)
                    {
                        key = kv.Key;
                        value = kv.Value;
                        string type = kv.Value.GetType().ToString();
                        //
                        if (column != null)
                        {
                            if (column == key)
                            {
                                if (upperLimit != null && type != "System.String")
                                {
                                    Double doubleValue = Convert.ToDouble(value);
                                    if (doubleValue >= upperLimit)
                                    {
                                        //Notification
                                        NotificationEntity notification = new NotificationEntity();
                                        Sensor sensor = sensorEntity.getSensorById_DAL(sensorId);
                                        notification.insertNotification_DAL(userId, sensor.Deviceid, "upper_limit_triggered", sensorId);
                                        //Email
                                        //  EmailSender email = new EmailSender();
                                        //UserDetails user = userEntity.getUserById_DAL(userId);
                                        // Task.Factory.StartNew(() => email.sendEmail(user.EmailAddress, "sensor id: " + sensorId + " has been triggered", "Dear " + user.FirstName + ", \n \n the upper limit of your sensor has been triggered. \n \n Regards, \n UCL Gadgeteer Team"));
                                    }
                                }
                                if (lowerLimit != null && type != "System.String")
                                {
                                    Double doubleValue = Convert.ToDouble(value);
                                    if (doubleValue <= lowerLimit)
                                    {
                                        //Notification 
                                        NotificationEntity notification = new NotificationEntity();
                                        Sensor sensor = sensorEntity.getSensorById_DAL(sensorId);
                                        notification.insertNotification_DAL(userId, sensor.Deviceid, "lower_limit_triggered", sensorId);
                                        //Email
                                        //    EmailSender email = new EmailSender();
                                        //  UserDetails user = userEntity.getUserById_DAL(userId);
                                        //Task.Factory.StartNew(() => email.sendEmail(user.EmailAddress, "sensor id: " + sensorId + " has been triggered", "Dear " + user.FirstName + ", \n \n the lower limit of your sensor has been triggered. \n \n Regards, \n UCL Gadgeteer Team"));
                                    }
                                }
                            }
                        }
                        //
                        key = "\"" + key + "\"";
                        if (key == "\"time\"")
                        {
                            string timeValue = value.ToString();
                            CultureInfo ukCulture = new CultureInfo("en-GB");
                            DateTime dt = DateTime.Parse(timeValue, ukCulture.DateTimeFormat);
                            string timestamp = dt.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
                            value = timestamp;
                        }
                        //
                        if (type != "System.String")
                        {
                        }
                        else
                        {
                            value = "\"" + value + "\"";
                        }
                        document += "," + key + ":" + value;
                    }
                    document += " }";
                    var bsonDoc = BsonDocument.Parse(document);
                    insertDocument(bsonDoc, sensorType);
                }
            }
            catch (KeyNotFoundException ex)
            {
                throw ex;
            }
            //
        }

        /// <summary>
        /// removes the specified document from the given collection
        /// </summary>
        /// <param name="id">document id</param>
        /// <param name="type">sensor type</param>
        public void removeDocumentById_DAL(string id, string type)
        {
            var collection = database.GetCollection(type);
            var id2 = ObjectId.Parse(id);
            var query = Query.EQ("_id", id2);
            collection.Remove(query);
        }

        ///// <summary>
        ///// updates the id of a sensor
        ///// </summary>
        ///// <param name="id"></param>
        ///// <param name="type"></param>
        //public void updateId_DAL(int id, int newId, string type)
        //{
        //    var collection = database.GetCollection(type);
        //    var query = Query.EQ("sensorId", id);
        //    var update = Update.Set("sensorId", newId);
        //    collection.Update(query, update, UpdateFlags.Multi);

        //}

        /// <summary>
        /// returns the data associated with the given sensor id and within the specified time period.
        /// </summary>
        /// <param name="sensorId">sensor id</param>
        /// <param name="startTime">start time</param>
        /// <param name="endTime">end time</param>
        /// <param name="pageSize">page size</param>
        /// <param name="sensorType">sensor type</param>
        /// <returns>json string</returns>
        public string getDataByIdandTime_DAL(int sensorId, DateTime startTime, DateTime endTime, int pageSize, string sensorType)
        {
            //get Collection
            var collection = database.GetCollection(sensorType);
            string startTimeString = startTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
            string endTimeString = endTime.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'");
            //
            var query = Query.And(Query.GTE("time", startTimeString), Query.LTE("time", endTimeString), Query.EQ("sensorId", sensorId));
            //
            MongoCursor cursor = collection.Find(query).SetSortOrder(SortBy.Ascending("time")).SetFields(Fields.Exclude("_id","sensorId"));
            cursor.Limit = pageSize;
            string json = cursor.ToJson();
            return json;
        }
        ////
        #region Private methods
        private MongoDatabase getDatabase(string connectionString,string database)
        {
           // string connectionString = "mongodb://10.4.2.4:27017/test";
           //  string connectionString = "mongodb://uclmongovm.cloudapp.net:27017/test";
            //string connectionString = "mongodb://iotworkerroles.cloudapp.net:27017/test";

            var client = new MongoClient(connectionString);
            var server = client.GetServer();
            return server.GetDatabase(database);
             
            //var client = new MongoClient(connectionString);
            //var server = client.GetServer();
            //return server.GetDatabase("test");
 
            //string connectionString = "mongodb://iot:gadg3t33r@ds031108.mongolab.com:31108/iot";
            //var client = new MongoClient(connectionString);
            //var server = client.GetServer();
            //return server.GetDatabase("iot");
        }
        private void addIndex()
        {
            //get Collection
            var collection = database.GetCollection("testSensor");
            collection.EnsureIndex("sensorId");
        }
        private void insertDocument(BsonDocument document, string sensorType)
        {
            var collection = database.GetCollection(sensorType);
            collection.Insert(document);
        }
        #endregion
    }

}